from starlette.types import ASGIApp, Receive, Scope, Send
from starlette.requests import Request
from sqlmodel import insert

from starlette.routing import Route
import logging
from datetime import datetime
import asyncio
import re

from ..dto.OperateLog import OperateLog
from ..init import AopMap
from ..init.Global import SessionFactory
from ..utils.JwtUtils import JwtUtils
from ..mapper import table


class LogMiddleware:
    """
    日志中间件
    用来记录谁对员工表做了增删改的操作
    """

    def __init__(self, app: ASGIApp):
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send):
        params = self.find(scope)
        (
            await self.app(scope, receive, send)
            if params is None
            else await self.myCallback(scope, receive, send, params)
        )

    def find(self, scope: Scope):
        if scope["type"] != "http":
            return None
        request = Request(scope)
        path = request.url.path
        method = request.method

        app = self.app

        while not hasattr(app, "routes"):
            app = app.app

        for route in app.routes:
            if self.myEq(path, method, route):
                if route.endpoint in AopMap:
                    return AopMap[route.endpoint]
                return None
        return None

    def myEq(self, path, method, route: Route):
        if method not in route.methods:
            return False
        pattern = re.sub(r"{\w+}", r"([^/]+)", route.path)
        pattern = f"^{pattern}$"
        return re.fullmatch(pattern, path) is not None

    async def myCallback(
        self, scope: Scope, receive: Receive, send: Send, params: tuple
    ):
        data = JwtUtils.decode(Request(scope).headers.get("token"))
        operateUser = data.get("id")
        operateTime = datetime.now()
        startTime = datetime.now()
        endTime = None
        returnValue = None
        className, methodName, methodParams = params

        async def send_wrapper(message: dict):
            nonlocal endTime, returnValue
            if "body" in message:
                endTime = datetime.now()
                returnValue = message.get("body").decode("utf-8")
            await send(message)

        await self.app(scope, receive, send_wrapper)

        costTime = int((endTime - startTime).microseconds / 1000)
        asyncio.create_task(
            self.log_async(
                className,
                methodName,
                methodParams,
                operateUser,
                operateTime,
                costTime,
                returnValue,
            )
        )

    async def log_async(
        self,
        className: str,
        methodName: str,
        methodParams: str,
        operateUser: int,
        operateTime: datetime,
        costTime: int,
        returnValue: str,
    ):
        async with SessionFactory() as session:
            async with session.begin():
                await session.execute(
                    insert(table.operateLogTable).values(
                        OperateLog(
                            className=className,
                            methodName=methodName,
                            methodParams=methodParams,
                            operateTime=operateTime,
                            operateUser=operateUser,
                            costTime=costTime,
                            returnValue=returnValue,
                        ).model_dump(exclude_none=True)
                    )
                )
